#include "CpuZ80.h"
#include "CpuZ80Macros.h"

#include <cstdio>

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file defines all the implementations of the un-prefixed opcodes.
//
// Useful shortcuts in Visual Studio 2010 :3
// 
// Ctrl+M Ctrl+M -> Collapse/Open current region
// Ctrl+M Ctrl+O -> Collapse current region recursively
// Ctrl+M Ctrl+L -> Expand all regions
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

byte_t CpuZ80::HandleOpcode(byte_t opcode)
{
	switch (opcode)
	{

	// Forward prefixed opcodes
	case 0xCB: return HandleOpcode_CB(FetchByte()); break; 
	case 0xDD: return HandleOpcode_DD(FetchByte()); break;
	case 0xED: return HandleOpcode_ED(FetchByte()); break;
	case 0xFD: return HandleOpcode_FD(FetchByte()); break;

#pragma region Exchange

	case 0x08: 
	{
		word_t tmp = RegAF;
		RegAF = RegAF1;
		RegAF1 = tmp;
	} break;

	// EXX
	case 0xD9:
	{
		word_t temp;
		temp = RegBC; RegBC = RegBC1; RegBC1 = temp;
		temp = RegDE; RegDE = RegDE1; RegDE1 = temp;
		temp = RegHL; RegHL = RegHL1; RegHL1 = temp;
	} break;

	case 0xE3:
	{
		word_t temp = ReadWord(RegSP);
		WriteWord(RegHL, RegSP);
		RegHL = temp;
	} break;

#pragma endregion

#pragma region 8-Bit Load

	case 0x40: RegB = RegB; break;
	case 0x41: RegB = RegC; break;
	case 0x42: RegB = RegD; break;
	case 0x43: RegB = RegE; break;
	case 0x44: RegB = RegH; break;
	case 0x45: RegB = RegL; break;
	case 0x46: RegB = ReadByte(RegHL); break;
	case 0x47: RegB = RegA; break;

	case 0x48: RegC = RegB; break;
	case 0x49: RegC = RegC; break;
	case 0x4A: RegC = RegD; break;
	case 0x4B: RegC = RegE; break;
	case 0x4C: RegC = RegH; break;
	case 0x4D: RegC = RegL; break;
	case 0x4E: RegC = ReadByte(RegHL); break;
	case 0x4F: RegC = RegA; break;

	case 0x50: RegD = RegB; break;
	case 0x51: RegD = RegC; break;
	case 0x52: RegD = RegD; break;
	case 0x53: RegD = RegE; break;
	case 0x54: RegD = RegH; break;
	case 0x55: RegD = RegL; break;
	case 0x56: RegD = ReadByte(RegHL); break;
	case 0x57: RegD = RegA; break;

	case 0x58: RegE = RegB; break;
	case 0x59: RegE = RegC; break;
	case 0x5A: RegE = RegD; break;
	case 0x5B: RegE = RegE; break;
	case 0x5C: RegE = RegH; break;
	case 0x5D: RegE = RegL; break;
	case 0x5E: RegE = ReadByte(RegHL); break;
	case 0x5F: RegE = RegA; break;

	case 0x60: RegH = RegB; break;
	case 0x61: RegH = RegC; break;
	case 0x62: RegH = RegD; break;
	case 0x63: RegH = RegE; break;
	case 0x64: RegH = RegH; break;
	case 0x65: RegH = RegL; break;
	case 0x66: RegH = ReadByte(RegHL); break;
	case 0x67: RegH = RegA; break;

	case 0x68: RegL = RegB; break;
	case 0x69: RegL = RegC; break;
	case 0x6A: RegL = RegD; break;
	case 0x6B: RegL = RegE; break;
	case 0x6C: RegL = RegH; break;
	case 0x6D: RegL = RegL; break;
	case 0x6E: RegL = ReadByte(RegHL); break;
	case 0x6F: RegL = RegA; break;

	case 0x70: WriteByte(RegB, RegHL); break;
	case 0x71: WriteByte(RegC, RegHL); break;
	case 0x72: WriteByte(RegD, RegHL); break;
	case 0x73: WriteByte(RegE, RegHL); break;
	case 0x74: WriteByte(RegH, RegHL); break;
	case 0x75: WriteByte(RegL, RegHL); break;
	case 0x77: WriteByte(RegA, RegHL); break;

	case 0x78: RegA = RegB; break;
	case 0x79: RegA = RegC; break;
	case 0x7A: RegA = RegD; break;
	case 0x7B: RegA = RegE; break;
	case 0x7C: RegA = RegH; break;
	case 0x7D: RegA = RegL; break;
	case 0x7E: RegA = ReadByte(RegHL); break;
	case 0x7F: RegA = RegA; break;

#pragma endregion

#pragma region 8-Bit Immediate Load

	case 0x06: RegB = FetchByte();									break;
	case 0x0E: RegC = FetchByte();									break;
	case 0x16: RegD = FetchByte();									break;
	case 0x1E: RegE = FetchByte();									break;
	case 0x26: RegH = FetchByte();									break;
	case 0x2E: RegL = FetchByte();									break;
	case 0x36: WriteByte(FetchByte(), RegHL);						break;
	case 0x3E: RegA = FetchByte();									break;

#pragma endregion

#pragma region 8-Bit Indirect Load and Store

	case 0x0A: RegA = ReadByte(RegBC);								break;
	case 0x1A: RegA = ReadByte(RegDE);								break;
	case 0x3A: RegA = ReadByte(FetchWord());						break;

	case 0x02: WriteByte(RegA, RegBC);								break;
	case 0x12: WriteByte(RegA, RegDE);								break;
	case 0x32: WriteByte(RegA, FetchWord());					break;

#pragma endregion
		
#pragma region 8-Bit Arithmetic And Logical

	case 0x80: AddByte(RegB); break;
	case 0x81: AddByte(RegC); break;
	case 0x82: AddByte(RegD); break;
	case 0x83: AddByte(RegE); break;
	case 0x84: AddByte(RegH); break;
	case 0x85: AddByte(RegL); break;
	case 0x86: AddByte(ReadByte(RegHL)); break;
	case 0x87: AddByte(RegA); break;

	case 0x88: AddByte(RegB, true); break;
	case 0x89: AddByte(RegC, true); break;
	case 0x8A: AddByte(RegD, true); break;
	case 0x8B: AddByte(RegE, true); break;
	case 0x8C: AddByte(RegH, true); break;
	case 0x8D: AddByte(RegL, true); break;
	case 0x8E: AddByte(ReadByte(RegHL), true); break;
	case 0x8F: AddByte(RegA, true); break;

	case 0x90: SubByte(RegB); break;
	case 0x91: SubByte(RegC); break;
	case 0x92: SubByte(RegD); break;
	case 0x93: SubByte(RegE); break;
	case 0x94: SubByte(RegH); break;
	case 0x95: SubByte(RegL); break;
	case 0x96: SubByte(ReadByte(RegHL)); break;
	case 0x97: SubByte(RegA); break;

	case 0x98: SubByte(RegB, true); break;
	case 0x99: SubByte(RegC, true); break;
	case 0x9A: SubByte(RegD, true); break;
	case 0x9B: SubByte(RegE, true); break;
	case 0x9C: SubByte(RegH, true); break;
	case 0x9D: SubByte(RegL, true); break;
	case 0x9E: SubByte(ReadByte(RegHL), true); break;
	case 0x9F: SubByte(RegA, true); break;	

	case 0xA0: AndByte(RegB); break;
	case 0xA1: AndByte(RegC); break;
	case 0xA2: AndByte(RegD); break;
	case 0xA3: AndByte(RegE); break;
	case 0xA4: AndByte(RegH); break;
	case 0xA5: AndByte(RegL); break;
	case 0xA6: AndByte(ReadByte(RegHL)); break;
	case 0xA7: AndByte(RegA); break;

	case 0xA8: XorByte(RegB); break;
	case 0xA9: XorByte(RegC); break;
	case 0xAA: XorByte(RegD); break;
	case 0xAB: XorByte(RegE); break;
	case 0xAC: XorByte(RegH); break;
	case 0xAD: XorByte(RegL); break;
	case 0xAE: XorByte(ReadByte(RegHL)); break;
	case 0xAF: XorByte(RegA); break;

	case 0xB0: OrByte(RegB); break;
	case 0xB1: OrByte(RegC); break;
	case 0xB2: OrByte(RegD); break;
	case 0xB3: OrByte(RegE); break;
	case 0xB4: OrByte(RegH); break;
	case 0xB5: OrByte(RegL); break;
	case 0xB6: OrByte(ReadByte(RegHL)); break;
	case 0xB7: OrByte(RegA); break;

	case 0xB8: CompareByte(RegB); break;
	case 0xB9: CompareByte(RegC); break;
	case 0xBA: CompareByte(RegD); break;
	case 0xBB: CompareByte(RegE); break;
	case 0xBC: CompareByte(RegH); break;
	case 0xBD: CompareByte(RegL); break;
	case 0xBE: CompareByte(ReadByte(RegHL)); break;
	case 0xBF: CompareByte(RegA); break;

#pragma endregion

#pragma region 8-Bit Immediate Arithmetic and Logical

	case 0xC6: AddByte(FetchByte());								break;
	case 0xCE: AddByte(FetchByte(), true);							break;
	case 0xD6: SubByte(FetchByte());								break;
	case 0xDE: SubByte(FetchByte(), true);							break;
	case 0xE6: AndByte(FetchByte());								break;
	case 0xEE: XorByte(FetchByte());								break;
	case 0xF6: OrByte(FetchByte());									break;
	case 0xFE: CompareByte(FetchByte());							break;

#pragma endregion

#pragma region 8-Bit Increment and Decrement

	case 0x05: RegB = DecrementByte(RegB);							break;
	case 0x0D: RegC = DecrementByte(RegC);							break;
	case 0x15: RegD = DecrementByte(RegD);							break;
	case 0x1D: RegE = DecrementByte(RegE);							break;
	case 0x25: RegH = DecrementByte(RegH);							break;
	case 0x2D: RegL = DecrementByte(RegL);							break;
	case 0x35: WriteByte(DecrementByte(ReadByte(RegHL)), RegHL);	break; // (HL) <- (HL) - 1
	case 0x3D: RegA = DecrementByte(RegA);							break;

	case 0x04: RegB = IncrementByte(RegB);							break;
	case 0x0C: RegC = IncrementByte(RegC);							break;
	case 0x14: RegD = IncrementByte(RegD);							break;
	case 0x1C: RegE = IncrementByte(RegE);							break;
	case 0x24: RegH = IncrementByte(RegH);							break;
	case 0x2C: RegL = IncrementByte(RegL);							break;
	case 0x34: WriteByte(IncrementByte(ReadByte(RegHL)), RegHL);	break; // (HL) <- (HL) + 1
	case 0x3C: RegA = IncrementByte(RegA);							break;
	
	

#pragma endregion

#pragma region Rotate and Shift

	// RLCA
	case 0x07:
	{
		byte_t bit_seven = (RegA & 0x80) >> 7;
		
		SetFlag(Flag_Carry, bit_seven > 0);
		SetFlag(Flag_Negative, false);
		SetFlag(Flag_HC, false);
		
		RegA = (RegA << 1) | bit_seven;

		SetFlag(Flag_F3, (RegA & static_cast<byte_t>(Flag_F3)) > 0);
		SetFlag(Flag_F5, (RegA & static_cast<byte_t>(Flag_F5)) > 0);
	}																	break;

	// RRCA
	case 0x0F:
	{
		byte_t bit_zero = RegA & 1;
		
		SetFlag(Flag_Carry, bit_zero > 0);
		SetFlag(Flag_Negative, false);
		SetFlag(Flag_HC, false);

		RegA >>= 1;

		if (bit_zero > 0) RegA |= 0x80;

		SetFlag(Flag_F3, (RegA & static_cast<byte_t>(Flag_F3)) > 0);
		SetFlag(Flag_F5, (RegA & static_cast<byte_t>(Flag_F5)) > 0);
	}																	break;

	// RLA
	case 0x17:
	{
		byte_t res = RegA << 1;
		byte_t carry = IsFlagSet(Flag_Carry) ? 1 : 0;

		SetFlag(Flag_Negative, false);
		SetFlag(Flag_HC, false);
		SetFlag(Flag_Carry, (RegA & 0x80) > 0);

		RegA = res | carry;
		
		SetFlag(Flag_F3, (RegA & static_cast<byte_t>(Flag_F3)) > 0);
		SetFlag(Flag_F5, (RegA & static_cast<byte_t>(Flag_F5)) > 0);
	}																	break;

	// RRA
	case 0x1F:
	{
		byte_t res = RegA >> 1;
		byte_t carry = IsFlagSet(Flag_Carry) ? 0x80 : 0;

		SetFlag(Flag_Carry, (RegA & 1) > 0);
		SetFlag(Flag_Negative, false);
		SetFlag(Flag_HC, false);

		RegA = res | carry;

		SetFlag(Flag_F3, (RegA & static_cast<byte_t>(Flag_F3)) > 0);
		SetFlag(Flag_F5, (RegA & static_cast<byte_t>(Flag_F5)) > 0);
	}																	break;

#pragma endregion

#pragma region 16-Bit Load

	case 0xE9: RegPC = RegHL;											break;
	case 0xF9: RegSP = RegHL;											break;

#pragma endregion

#pragma region 16-Bit Immediate Load

	case 0x01: RegBC = FetchWord();										break;		// LD BC, word
	case 0x11: RegDE = FetchWord();										break; 
	case 0x21: RegHL = FetchWord();										break;
	case 0x31: RegSP = FetchWord();										break;

#pragma endregion

#pragma region 16-Bit Indirect Load

	case 0x2A: RegHL = ReadWord(FetchWord());							break;
	case 0x22: WriteWord(RegHL, FetchWord());							break;

#pragma endregion

#pragma region 16-Bit Arithmetic and Logical

	case 0x09: RegHL = AddWord(RegHL, RegBC);							break;
	case 0x19: RegHL = AddWord(RegHL, RegDE);							break;
	case 0x29: RegHL = AddWord(RegHL, RegHL);							break;
	case 0x39: RegHL = AddWord(RegHL, RegSP);							break;

#pragma endregion

#pragma region 16-Bit Increment and Decrement

	case 0x03: RegBC++;													break;
	case 0x13: RegDE++;													break;
	case 0x23: RegHL++;													break;
	case 0x33: RegSP++;													break;

	case 0x0B: RegBC--;													break;
	case 0x1B: RegDE--;													break;
	case 0x2B: RegHL--;													break;
	case 0x3B: RegSP--;													break;

#pragma endregion

#pragma region Stack

	case 0xC1: RegBC = PopWord();										break;
	case 0xD1: RegDE = PopWord();										break;
	case 0xE1: RegHL = PopWord();										break;
	case 0xF1: RegAF = PopWord();										break;

	case 0xC5: PushWord(RegBC);											break;
	case 0xD5: PushWord(RegDE);											break;
	case 0xE5: PushWord(RegHL);											break;
	case 0xF5: PushWord(RegAF);											break;

#pragma endregion

#pragma region Exchange

	case 0xEB: 
		{
			word_t temp = RegDE;
			RegDE = RegHL;
			RegHL = temp;
		}																break;

#pragma endregion

#pragma region Jumps, Calls, Returns

	case 0x10:
		{
			byte_t disp = FetchByte();
			--RegB;

			if (RegB > 0) 
			{
				JumpOnCondition(AddDisplacement(RegPC, disp), true);
			}
		} break;

	case 0x18: JumpOnCondition(AddDisplacement(RegPC, FetchByte()), true); break;

	case 0x20: JumpOnCondition(AddDisplacement(RegPC, FetchByte()), !IsFlagSet(Flag_Zero)); break;	// If NZ, PC <- PC + index
	case 0x28: JumpOnCondition(AddDisplacement(RegPC, FetchByte()),  IsFlagSet(Flag_Zero)); break;	// If Z, PC <- PC + index
	case 0x30: JumpOnCondition(AddDisplacement(RegPC, FetchByte()), !IsFlagSet(Flag_Carry)); break;	// If NC, PC <- PC + index
	case 0x38: JumpOnCondition(AddDisplacement(RegPC, FetchByte()),  IsFlagSet(Flag_Carry)); break;	// If C, PC <- PC + index

	case 0xC3: JumpOnCondition(FetchWord(), true);						break;
	case 0xC2: JumpOnCondition(FetchWord(), !IsFlagSet(Flag_Zero));		break;
	case 0xCA: JumpOnCondition(FetchWord(),  IsFlagSet(Flag_Zero));		break;
	case 0xD2: JumpOnCondition(FetchWord(), !IsFlagSet(Flag_Carry));	break;
	case 0xDA: JumpOnCondition(FetchWord(),  IsFlagSet(Flag_Carry));	break;
	case 0xE2: JumpOnCondition(FetchWord(), !IsFlagSet(Flag_Parity));	break;
	case 0xEA: JumpOnCondition(FetchWord(),  IsFlagSet(Flag_Parity));	break;
	case 0xF2: JumpOnCondition(FetchWord(), !IsFlagSet(Flag_Sign));		break;
	case 0xFA: JumpOnCondition(FetchWord(),  IsFlagSet(Flag_Sign));		break;
	
	case 0xC9: ReturnOnCondition(true);									break;
	case 0xC0: ReturnOnCondition(!IsFlagSet(Flag_Zero));				break;
	case 0xC8: ReturnOnCondition( IsFlagSet(Flag_Zero));				break;
	case 0xD0: ReturnOnCondition(!IsFlagSet(Flag_Carry));				break;
	case 0xD8: ReturnOnCondition( IsFlagSet(Flag_Carry));				break;
	case 0xE0: ReturnOnCondition(!IsFlagSet(Flag_Parity));				break;
	case 0xE8: ReturnOnCondition( IsFlagSet(Flag_Parity));				break;
	case 0xF0: ReturnOnCondition(!IsFlagSet(Flag_Sign));				break;
	case 0xF8: ReturnOnCondition( IsFlagSet(Flag_Sign));				break;

	case 0xCD: CallOnCondition(FetchWord(), true);						break;
	case 0xC4: CallOnCondition(FetchWord(), !IsFlagSet(Flag_Zero));		break;
	case 0xCC: CallOnCondition(FetchWord(),  IsFlagSet(Flag_Zero));		break;
	case 0xD4: CallOnCondition(FetchWord(), !IsFlagSet(Flag_Carry));	break;
	case 0xDC: CallOnCondition(FetchWord(),  IsFlagSet(Flag_Carry));	break;
	case 0xE4: CallOnCondition(FetchWord(), !IsFlagSet(Flag_Parity));	break;
	case 0xEC: CallOnCondition(FetchWord(),  IsFlagSet(Flag_Parity));	break;
	case 0xF4: CallOnCondition(FetchWord(), !IsFlagSet(Flag_Sign));		break;
	case 0xFC: CallOnCondition(FetchWord(),  IsFlagSet(Flag_Sign));		break;

	case 0xC7: CallOnCondition(0x0000, true);							break; // RST0
	case 0xCF: CallOnCondition(0x0008, true);							break; // RST1
	case 0xD7: CallOnCondition(0x0010, true);							break; // RST2
	case 0xDF: CallOnCondition(0x0018, true);							break; // RST3
	case 0xE7: CallOnCondition(0x0020, true);							break; // RST4
	case 0xEF: CallOnCondition(0x0028, true);							break; // RST5
	case 0xF7: CallOnCondition(0x0030, true);							break; // RST6
	case 0xFF: CallOnCondition(0x0038, true);							break; // RST7

		
#pragma endregion

#pragma region CPU Control

	case 0x00:														break;
	
	// SSC
	case 0x37: 
		SetFlag(Flag_Carry, true);
		SetFlag(Flag_HC, false); 
		SetFlag(Flag_Negative, false); 
		SetFlag(Flag_F3, (RegA & static_cast<byte_t>(Flag_F3)) > 0);
		SetFlag(Flag_F5, (RegA & static_cast<byte_t>(Flag_F5)) > 0);
		break;

	case 0x27: DecimalAdjustAccumulator();							break;

	case 0x76: m_isHalted = true; --RegPC;							break; // HALT

	case 0xF3:	m_iff1 = m_iff2 = false;							break; // DI
	case 0xFB:	m_iff1 = m_iff2 = true;								break; // EI

	// CPL
	case 0x2F: 
		RegA = ~RegA; 
		SetFlag(Flag_HC, true); 
		SetFlag(Flag_Negative, true);				
		SetFlag(Flag_F3, (RegA & static_cast<byte_t>(Flag_F3)) > 0);
		SetFlag(Flag_F5, (RegA & static_cast<byte_t>(Flag_F5)) > 0);
		break; 

	// CCF
	case 0x3F:
		SetFlag(Flag_HC, IsFlagSet(Flag_Carry));
		SetFlag(Flag_Carry, !IsFlagSet(Flag_Carry));
		SetFlag(Flag_Negative, false);
		SetFlag(Flag_F3, (RegA & static_cast<byte_t>(Flag_F3)) > 0);
		SetFlag(Flag_F5, (RegA & static_cast<byte_t>(Flag_F5)) > 0);
		break;

#pragma endregion

	case 0xD3: WriteBytePort(RegA, FetchByte()); break; // OUT (n),A

	default:

		printf("%02X: Unrecognized Opcode\n", opcode);
		m_isHalted = true;

		break;

	}

	return m_cycles[opcode];
}

